/*
 * mod_grammargen.c
 *
 *  Created on: Jan 16, 2012
 *      Author: robert
 */

//#include "mod_grammargen.h"

#include <libxml/HTMLparser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

#include <httpd.h>
#include <http_log.h>
#include <http_config.h>
#include <util_filter.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define JSGF_PUBLIC_RULE 1
#define JSGF_PRIVATE_RULE 2

#define JSGF_SUCCESS 3
#define JSGF_FAILURE 4
#define CHUNK 60

static const char* filter_name = "grammargen-filter" ;

typedef struct jsgf_rule_t {
	unsigned int type;
	struct jsgf_rule_t *prev;
	struct jsgf_rule_t *next;

	const char *rule;
	const char *name;
} jsgf_rule_t;

typedef struct jsgf_t {
	jsgf_rule_t *first;
	jsgf_rule_t *last;
	unsigned int num_rules;
	const char *name;
} jsgf_t;

static xmlChar *get_attr_value(xmlNodePtr node, const char* attr_key);
static xmlChar *get_node_value(xmlNodePtr node);
static void print_element_names(ap_filter_t* f, htmlNodePtr node, int depth);
static void print_xpath_element_names(ap_filter_t* f, xmlDocPtr doc, const unsigned char *xpath);

void jsgf_free_grammar(jsgf_t *grammar) {
	free(grammar);
}

static jsgf_t *jsgf_create_empty_grammar(const char *grammar_name) {
	jsgf_t *grammar = (jsgf_t*)malloc(sizeof(jsgf_t));

	if (grammar == NULL) {
		return NULL;
	}

	grammar->num_rules = 0;
	grammar->first = NULL;
	grammar->last = NULL;
	grammar->name = grammar_name;

	return grammar;
}

jsgf_rule_t *jsgf_create_empty_rule() {
	jsgf_rule_t *rule = (jsgf_rule_t*)malloc(sizeof(jsgf_rule_t));
	rule->name = NULL;
	rule->next = NULL;
	rule->prev = NULL;
	rule->type = JSGF_PUBLIC_RULE;

	return rule;
}

static int jsgf_add_rule(ap_filter_t* f, jsgf_t *grammar, jsgf_rule_t *rule) {
	rule->next = NULL;

	if (grammar->first == NULL) {
		// rule is the first
		grammar->first = rule;
		grammar->last = rule;
	} else if (grammar->last != NULL){
		jsgf_rule_t *old_last = grammar->last;
		old_last->next = rule;
		grammar->last = rule;
		rule->prev = old_last;
	} else {
		// illegal linked list state
	}

	return JSGF_SUCCESS;
}

static int write_jsgf_file(ap_filter_t* f, jsgf_t* grammar, const char *filename) {
	apr_file_t *file;
	apr_status_t rv;

	rv = apr_file_open(&file, "/home/robert/test.jsgf", APR_READ | APR_WRITE | APR_SHARELOCK, APR_OS_DEFAULT ,f->r->pool);
	if (rv != APR_SUCCESS) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "could not open file");
		return JSGF_FAILURE;
	}

	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "file open");
	apr_file_printf(file, "#JSGF V1.0;\n\n");
	//fprintf(fd, "#JSGF V1.0;\n\n");
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "#JSGF V1.0;");
	apr_file_printf(file, "grammar %s;\n", grammar->name);
	//fprintf(fd, "grammar %s;\n", grammar->name);
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "%s;", grammar->name);

	jsgf_rule_t *cur = grammar->first;

	while (cur != NULL) {
		//fprintf(fd, cur->type == JSGF_PUBLIC_RULE ? "public " : "private ");
		apr_file_printf(file,  cur->type == JSGF_PUBLIC_RULE ? "public " : "private ");
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, cur->type == JSGF_PUBLIC_RULE ? "public " : "private ");
		//fprintf(fd, "<%s> = %s;\n", cur->name, cur->rule);
		apr_file_printf(file, "<%s> = %s;\n", cur->name, cur->rule);
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "<%s> = %s;\n", cur->name, cur->rule);

		cur = cur->next;
	}
	apr_file_flush(file);
	apr_file_close(file);

	return JSGF_SUCCESS;
}

static void write_grammar(ap_filter_t* f, xmlNodeSetPtr nodes) {
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "writing grammar");
	// print the nodes
	xmlNodePtr cur;
	int size = size = (nodes) ? nodes->nodeNr : 0;
	jsgf_t *grammar =  jsgf_create_empty_grammar("test");
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "%d links found", size);
	int i;
	for (i = 0; i < size; i++) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "link #%d", i);
		if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
			cur = nodes->nodeTab[i];

			xmlChar *href = get_attr_value(cur, (const char*)"href");
			xmlChar *linktext = get_node_value(cur);

			// linktext is the new command
			jsgf_rule_t *rule = jsgf_create_empty_rule();

			rule->name = (const char*)href;
			rule->rule = (const char*)linktext;

			jsgf_add_rule(f, grammar, rule);
		} else {
			cur = nodes->nodeTab[i];
		}
	}

	write_jsgf_file(f, grammar, "/home/robert/test.jsgf");

	jsgf_free_grammar(grammar);
}

static xmlChar *get_attr_value(xmlNodePtr node, const char* attr_key) {
	if (node->type != XML_ELEMENT_NODE)
		return NULL;

	xmlAttrPtr attr = node->properties;
	while (attr != NULL) {
		if (xmlStrcasecmp(xmlCharStrdup(attr_key), attr->name) == 0) {
			xmlNodePtr val = attr->children;
			if (val == NULL || val->content == NULL) {
				continue;
			}

			return val->content;
		} else {
			continue;
		}
	}

	return NULL;
}

static xmlChar *get_node_value(xmlNodePtr node) {
	if (node) {
		if (node->type != XML_TEXT_NODE) {
			if (node->children && node->children->type == XML_TEXT_NODE) {
				node = node->children;
			} else {
				return NULL;
			}
		}

		xmlChar *node_value = node->content;
		return node_value;
	} else {
		return NULL;
	}
}

static void print_element_names(ap_filter_t* f, htmlNodePtr node, int depth) {
	htmlNodePtr cur_node = NULL;
	int i = 0;

	for (cur_node = node; cur_node; cur_node = cur_node->next, i++) {
		if (cur_node->type == XML_ELEMENT_NODE) {

			ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "%d - %s",i, cur_node->name);
		}

		print_element_names(f, cur_node->children, depth + 1);
	}
}

static void print_xpath_element_names(ap_filter_t* f, xmlDocPtr doc, const unsigned char *xpath) {
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "Using XPath expression '%s'", xpath);

	xmlXPathContextPtr xpathCtx;
	xmlXPathObjectPtr xpathObj;
	xpathCtx = xmlXPathNewContext(doc);
	if (xpathCtx == NULL) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "Unable to create new XPath context");
		xmlFreeDoc(doc);
		exit(1);
	}
	xpathObj = xmlXPathEvalExpression(xpath, xpathCtx);
	if (xpathObj == NULL) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "Unable to evaluate XPath Expression '%s'", xpath);
		xmlXPathFreeContext(xpathCtx);
		xmlFreeDoc(doc);
		exit(1);
	}

	xmlNodeSetPtr nodes = xpathObj->nodesetval;

	write_grammar(f, nodes);

	// print the nodes
	xmlNodePtr cur;
	int size = size = (nodes) ? nodes->nodeNr : 0;
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "found %d nodes", size);
	int i;
	for (i = 0; i < size; ++i) {
		if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
			cur = nodes->nodeTab[i];

			xmlChar* href = get_attr_value(cur, (const char*)"href");
			if (href) {
				ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "%d = element node \"%s\" -> [href] = '%s'", i, cur->name, href);
			} else {
				ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "%d = element node \"%s\"", i, cur->name);
			}
		} else {
			cur = nodes->nodeTab[i];
			ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "%d = node \"%s\": type %d",i, cur->name, cur->type);
		}
	}
}

static int grammargen_filter_init(ap_filter_t* f) {
	//ap_set_module_config(f->r->request_config, )

	return OK;
}

static int grammargen_filter(ap_filter_t* f, apr_bucket_brigade* bb) {
	htmlParserCtxtPtr ctxtPtr;
	htmlDocPtr docPtr;

	char buffer[8192];

	ctxtPtr = htmlCreateMemoryParserCtxt(buffer, 8192);
	docPtr = htmlCtxtReadFile(ctxtPtr, f->r->filename, "UTF-8",
			HTML_PARSE_NOWARNING);

	//htmlNodePtr html = docPtr->children->next;

	//print_element_names(f, html, 0);

	print_xpath_element_names(f, docPtr, (unsigned char *)"//a");

	xmlFreeParserCtxt(ctxtPtr);
	xmlFreeDoc(docPtr);


	/*
	htmlParserCtxtPtr ctxt;
	htmlDocPtr document;
	int res = 0;

	apr_bucket *bucket;
	const char *buffer;
	size_t bytes;
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker1");
	ctxt = htmlNewParserCtxt();

	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker2");
	for (bucket = APR_BRIGADE_FIRST(bb);
		 bucket != APR_BRIGADE_SENTINEL(bb);
		 bucket = APR_BUCKET_NEXT(bucket)) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker3");
		if (APR_BUCKET_IS_EOS(bucket)) {
			// end of data
		} else if (apr_bucket_read(bucket, &buffer, &bytes, APR_BLOCK_READ) == APR_SUCCESS) {
			//const char* p = buffer;

			// split the data into 60byte chunks and let libxml parse it
			char chunk[CHUNK];
			int count = bytes, i, count2 = 0;

			while (count > 0) {
				if (count >= CHUNK) {
					for (i = 0; i<CHUNK;i++) {
						chunk[i] = buffer[count2];
					}

					count -= CHUNK;
				} else {
					for (i = 0; i< count;i++) {
						chunk[i] = buffer[count2];
					}

					count = 0;
				}

				htmlParseChunk(ctxt, chunk, CHUNK, 0);

				count2 ++;
			}

		}
	}

	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker4");
	htmlParseChunk(ctxt, buffer, 0, 1);
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker4a");

	htmlParseDocument(ctxt);
	document = ctxt->myDoc;

	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker5");
	htmlFreeParserCtxt(ctxt);

	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker6");
	res = 1;
	if (!res) {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "The document is not well-formed and cannot be parsed");
	} else {
		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "marker7");
		print_xpath_element_names(f, document, (const unsigned char*)"//a");
	}
	xmlFreeDoc(document);
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "end");
	*/
	return ap_pass_brigade(f->next, bb) ;
}

static void grammargen_hooks(apr_pool_t *p) {
	ap_register_output_filter(filter_name, grammargen_filter, grammargen_filter_init, AP_FTYPE_RESOURCE);
}

module AP_MODULE_DECLARE_DATA grammargen_module = {
	STANDARD20_MODULE_STUFF,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	grammargen_hooks
} ;
